package com.captcha.generator;

import com.captcha.config.Configurable;
import com.captcha.exception.ConfigException;
import com.captcha.exception.TextException;
import com.captcha.utils.Base64Utils;
import com.captcha.utils.RandomUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @Author: 随风飘的云
 * @Description: 抽象验证码生成类
 * @Date: 2022/3/7 13:34
 * @Modified By:
 */
public abstract class AbstractCaptcha extends Configurable{

    private static final long serialVersionUID = 3180820918087507254L;

    // 设置外置字体
    public Font font;

    // 保存验证码图片的byte数据
    protected  byte[] imageBytes;

    public byte[] getImageBytes() {
        return imageBytes;
    }

    public void setImageBytes(byte[] imageBytes) {
        this.imageBytes = imageBytes;
    }

    /**
     * 获取字体
     * @return
     */
    public Font getFont() {
        return font;
    }

    /**
     *  设置不使用抽象方法设置外部字体
     * @return
     */
    public void setFont(Font font) {
        this.font = font;
    }

    // 设置字体默认30f大小
    public void setTextFont(String name, float fontSize){
        setTextFont(name, Font.BOLD, fontSize);
    }

    /**
     * 设置外部字体
     * 注意： resource提供的字体库来源于windows10系统字体库，仅仅支持英文映射，不支持大小写映射和中文映射
     * @param name
     * @param style
     * @param fontSize
     */
    public void setTextFont(String name, int style, float fontSize){
        try {
            this.font = Font.createFont(Font.TRUETYPE_FONT, new File(getClass().getResource("/" + name).getFile())).deriveFont(style, fontSize);
        } catch (Exception e) {
            throw new TextException("The loaded resource file does not exist: Please delete the ”target“ directory under the root directory and re-run the program: " + e);
        }
    }

    /**
     * 获取验证码内容
     * @return
     */
    public String getText(){
        String type = getConfig().getContentType();
        int length = getConfig().getTextLength();
        if(type != null){
            // 为了显示效果，算术验证码不能超过4位，比如说两个数x，y，则x.length+y.length<=4
            if(type.equals("ARITHMETIC") || type.equals("ARITHMETIC_ZH")){
                int value = Integer.valueOf(length);
                if(value > 4){
                    throw new TextException("The content length of the arithmetic verification code cannot exceed 4 digits");
                }
            }else if(type.equals("CHINESE_IDIOM")){
                // 成语不应该设置长度
                if(length != 4){
                    throw new TextException("The idiom verification code does not need to set the length");
                }
            }
        }
        CaptchaTextProducer textProducer = new CaptchaTextProducer();
        return textProducer.getCaptchaText(type, length);
    }

    /**
     * 设置字体颜色
     * @return
     */
    public Color getTextColor(){
        Color backGround = getConfig().getBackgroundColor();
        Color textColor;
        if(backGround != Color.WHITE){
           textColor = getConfig().getTextFontColor();
        }else {
            textColor = RandomUtils.randomColor();
        }
        return textColor;
    }

    public void FillBackGround(Graphics2D graphics, int width, int height){
        // 填充背景
        Color color = getConfig().getBackgroundColor();
        graphics.setColor(color == null ? Color.WHITE : color);
        graphics.fillRect(0, 0, width, height);
    }

    /**
     * 设置字体
     * @param graphics2D
     */
    public void setImageFont(Graphics2D graphics2D){
        // 如果外部字体为空，则说明需要加载内部字体
        if(font == null){
            Font[] fonts = getConfig().getTextFontType(getConfig().getTextFontSize());
            int fontLength = fonts.length;
            Font font = fonts[RandomUtils.randomInt(fontLength)];
            graphics2D.setFont(font);
        }else {
            graphics2D.setFont(font);
        }
    }

    public void addRenderingHints(Graphics2D graphics2D){
        // 设置抗拒齿
        RenderingHints hints = new RenderingHints(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_OFF);

        hints.add(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_QUALITY));
        hints.add(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));

        hints.add(new RenderingHints(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY));

        graphics2D.setRenderingHints(hints);
    }

    /**
     * 图形扭曲
     * @param graphics
     * @param width
     * @param height
     */
    public void Shear(Graphics2D graphics, int width, int height, Color color){
        addRenderingHints(graphics);

        ShearY(graphics, width, height, color);
        ShearX(graphics, width, height, color);

    }

    /**
     * 扭曲X轴方向的
     * @param g
     * @param width
     * @param height
     */
    private void ShearX(Graphics2D g, int width, int height, Color color){
        int period = RandomUtils.randomInt(width);
        int frame = 1;
        int phase = RandomUtils.randomInt(2);
        for (int i = 0; i < height; i++) {
            double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frame);
            g.copyArea(0, i, width, 1, (int) d, 0);
            g.setColor(color);
            g.drawLine((int) d, i, 0, i);
            g.drawLine((int) d + width, i, width, i);
        }
    }

    /**
     * 扭曲y轴方向的
     * @param g
     * @param width
     * @param height
     */
    private void ShearY(Graphics2D g, int width, int height, Color color){
        int period = RandomUtils.randomInt(height >> 1);
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < width; i++) {
            double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
            g.copyArea(i, 0, 1, height, 0, (int) d);
            g.setColor(color);
            // 擦除原位置的痕迹
            g.drawLine(i, (int) d, i, 0);
            g.drawLine(i, (int) d + height, i, height);
        }
    }

    /**
     * 画一定数量的干扰圆，信噪点
     * @param num 干扰圆的数量
     * @param g 画笔
     */
    public void drawCircle(int num, Graphics2D g){
        int width = getConfig().getWidth();
        int height = getConfig().getHeight();
        final ThreadLocalRandom random = RandomUtils.getRandom();

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        for (int i = 0; i < num; i++) {
            g.setColor(RandomUtils.randomColor());
            g.drawOval(random.nextInt(width - 25), random.nextInt(height - 15), 20,20);
        }
    }

    private int getNum(int min, int max){
        return min + RandomUtils.randomInt(max - min);
    }

    /**
     * 画干扰曲线(需要优化)
     * @param num 干扰线数量
     * @param g
     */
    public void drawBasselLine(int num, Graphics2D g){
        // 设置干扰线厚度
        int thickness = getConfig().getShadeThickness();
        g.setStroke(new BasicStroke((float) thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        // 抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int height = getConfig().getHeight();
        int width = getConfig().getWidth();

        for (int i = 0; i < num; i++) {
            g.setColor(RandomUtils.randomColor());
            int x1 = RandomUtils.randomInt(4,8);
            int y1 = getNum(x1, height / 2);
            int x2 = width - x1;
            int y2 = getNum(height / 2, height - x1);
            int nextX1 = getNum(width / 4, width / 4 * 3);
            int nextY1 = getNum(x1, height - x1);
            int center = width / 2;
            int LineType = RandomUtils.randomInt(3);
            if(LineType == 0){
                // 二阶贝赛尔曲线
                int temp = y1;
                y1 = y2;
                y2 = temp;
                QuadCurve2D quadCurve = new QuadCurve2D.Double();
                quadCurve.setCurve(x1, y1, nextX1, nextY1, x2, y2);
                g.draw(quadCurve);
            }else if(LineType == 1){
                // 三阶贝赛尔曲线
                int nextX2 = getNum(width / 4, width / 4 * 3);
                int nextY2 = getNum(x1, height - x1);
                CubicCurve2D cubicCurve = new CubicCurve2D.Double(x1, y1, nextX1, nextY1, nextX2, nextY2, x2, y2);
                g.draw(cubicCurve);
            }else{
                // 正弦曲线
                for (int j = 0; j < width; j += 1) {
                    x2 = j;
                    double widthX = -2*Math.PI*x2/(width*3/2);
                    // 根据公式计算 y = A*sin(widthX)
                    y2 = (int) ((height*3/2)*Math.sin(widthX));
                    g.drawLine(x1, y1+center, x2, y2+center);
                    x1 = x2;
                    y1 = y2;
                }
            }
        }
    }

    /**
     * 画边框
     * @param graphics2D
     */
    public void setImageBoard(Graphics2D graphics2D){
        int width = getConfig().getWidth();
        int height = getConfig().getHeight();

        // 获取边框颜色
        Color color = getConfig().setBorderColor();
        //边框厚度
        int thickness = getConfig().getThickness();

        graphics2D.setColor(color);
        // 设置画笔厚度
        BasicStroke stroke = new BasicStroke((float) thickness);
        graphics2D.setStroke(stroke);

        Line2D line2D = new Line2D.Double(0,0,0,width);
        graphics2D.draw(line2D);
        line2D = new Line2D.Double(0,0,width, 0);
        graphics2D.draw(line2D);
        line2D = new Line2D.Double(0, height-1, width, height-1);
        graphics2D.draw(line2D);
        line2D = new Line2D.Double(width-1, height-1,width-1, 0);
        graphics2D.draw(line2D);
    }

    public void createGif(String text){

    }

    public BufferedImage RenderImage(String text){
        return null;
    }

    /**
     * 使用Base64工具类编码图片
     * @return
     */
    public String Base64Encoder(){
        return Base64Utils.encode(getImageBytes());
    }

    /**
     * 将图片输出到本地
     */
    public void write(OutputStream out, BufferedImage image){
        try {
            ImageIO.write(image, "png" , out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将图片输出到本地
     */
    public void write(OutputStream out, byte[] imageByte){
        try {
            out.write(imageByte);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
